/******************************************************************
 * Created by: DO HOANG HAI                                       
 * Date of Creation: 26th/Sep/2007              
 * Date of last modification: 13th/Oct/2007
 *                                                                
 * Description: This class describes composite objects of resulting
 * item which match the search criteria
 * ...
 ******************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Pbdms.Common.DataObject;

namespace Pbdms.BusinessLogic.Search
{
    /// <summary>
    /// a search processor object is able to consume a search object to produce
    /// the list of matched resulting items.
    /// </summary>
    public class SearchProcessor
    {
        private SearchObj searchObject;
        private object lockObj=new object();
        //the request is triggered from local or from network
        private bool isLocalRequest;
        private double minMatchingPercentage = 50;
        private Network.NetworkController ntwCtrl;

        private double averagePointAward;  //each criterion matched will be given a mount of point (%)
                                            //if match all criteria -> 100 point will be awarded
        private ArrayList arlSearchResult;


        public SearchProcessor(Common.DataObject.SearchObj sObj, bool isLocalRequest, Network.NetworkController ntwCtrl)
        {
            this.searchObject = sObj;
            this.isLocalRequest = isLocalRequest;
            averagePointAward = Math.Round(pointAwardScheme(),1);
            this.ntwCtrl = ntwCtrl;
        }

        /// <summary>
        /// define the scheme for awarding point for each criterion matched.
        /// award average point for each tag and bibTex specification, double of
        /// avervate point for title and author
        /// </summary>
        /// <returns>average point awarded for each criterion matched</returns>
        private double pointAwardScheme()
        {
            int noOfAccount = searchObject.BibTexesStruct.Count;
            if (searchObject.Tags !=null)
                noOfAccount += searchObject.Tags.Count;
            foreach (object obj in searchObject.BibTexesStruct)
            {
                //award more point (double of normal) for title and author
                string btsName = ((Common.DataObject.BibTexStruct)obj).name;
                if (btsName.ToLower().Equals("title") || btsName.ToLower().Equals("author"))
                    noOfAccount++;
            }
            return 100.0 / noOfAccount;
        }

        public static SearchProcessor create(SearchObj sObj, bool isLocalRequest,Network.NetworkController ntwCtrl)
        {
            return new SearchProcessor(sObj,isLocalRequest, ntwCtrl );
        }

        /// <summary>
        /// start searching and return the results in an arraylist of ResultItem objects
        /// </summary>
        /// <returns></returns>
        public ArrayList getResult()
        {
            arlSearchResult = new ArrayList();
            
            //new thread for searching 
            System.Threading.ThreadStart ths = new System.Threading.ThreadStart(thsMethodGetResult);
            System.Threading.Thread th = new System.Threading.Thread(ths);
            th.Name = "Get Search result";
            th.Start();
            //if it is a local request, wait util thread finish then return the result             
           
            th.Join();
            //abort beyond finish searching
            th.Abort();
            return arlSearchResult;
            
        }

        /// <summary>
        /// sort results to be in decreasing order of matching percentage
        /// </summary>
        /// <param name="arlResult"></param>
        private void sortResult()
        {
            lock (lockObj)
            {
                Object[] arrObj = arlSearchResult.ToArray();
                arlSearchResult.Clear();

                //start sorting
                for (int i = 0; i < arrObj.Length - 1; i++)
                {
                    for (int j = 0; j < arrObj.Length - i - 1; j++)
                    {
                        if (((ResultItem)arrObj[j]).MatchPercentage < ((ResultItem)arrObj[j + 1]).MatchPercentage)
                        {
                            //swap 2 object
                            Object temp = arrObj[j];
                            arrObj[j] = arrObj[j + 1];
                            arrObj[j + 1] = temp;
                        }
                    }
                }
                //reconstruct the arraylist
                for (int i = 0; i < arrObj.Length; i++)
                    arlSearchResult.Add(arrObj[i]);
            }
        }

        private void searchNetwork()
        {
            //send the request to the network and assign the method to process the result on receive
            ntwCtrl.sendSearchRequest(this.searchObject , processReceivedSearchResult);
         
        }
        public void processReceivedSearchResult(object o1, Pbdms.NetworkDealer.EventArgs.SearchResultReceivedEventArgs e)
        {
            lock (lockObj)
            {
                System.Collections.ArrayList arl = e.ArlOfResultItems;

                foreach (object obj in arl)
                {
                    arlSearchResult.Add(obj);
                }
            }
        }
        private void thsMethodGetResult()
        {            
            //ArrayList arlResult;
            if (searchObject.SearchSource == SearchObj.SOURCE_LOCAL_ONLY) //search local only
                searchLocal();
            else if (searchObject.SearchSource == SearchObj.SOURCE_NETWORK_ONLY) //search network only
                searchNetwork();
            else
            {
                //search both local & network
                searchLocal();
                searchNetwork();
            }

            //sort the list by decreasing order of matching percentage
            sortResult();
        }

        /// <summary>
        /// search in local library for items which match the criteria specified by SearchObject
        /// </summary>        
        private void searchLocal()
        {
            //ArrayList arlSearchResult = new ArrayList();
            ArrayList arlOfItemIDs = DataController.ItemController.create().getListOfItemIDs();
            if (arlOfItemIDs != null)
            {
                foreach (object iID in arlOfItemIDs)
                {
                    string itemID = iID.ToString();
                    ResultItem resItem = new ResultItem();
                    resItem.ItemObj = DataController.ItemController.create().getItemFromDB(itemID);
                    resItem.FileInfoObj = DataController.FileInfoController.create().getFileInfoFromDB(itemID);
                    resItem.CommentObj = DataController.CommentController.create().getCommentFromDB(itemID);
                    resItem.SourceOfItem = ResultItem.ITEM_SOURCE_LOCAL;
                    if (isMatch(ref resItem, searchObject))
                        arlSearchResult.Add(resItem);
                }
            }
        }

        /// <summary>
        /// decide whether an item in the library matches with the search criteria.
        /// </summary>
        /// <param name="resItem"></param>
        /// <param name="searchObject"></param>
        /// <returns></returns>
        private bool isMatch(ref Common.DataObject.ResultItem resItem, SearchObj searchObject)
        {
            
            if (!isLocalRequest)
            {
                //if the request is a network request
                if (resItem.ItemObj.BPrivate)
                    return false;
                //else -> modify the location of the object
                String myIPAddress = NetworkDealer.NetworkDealer.ipAddMyComputer.ToString();
                resItem.FileInfoObj.Location = myIPAddress;
            }
            if ((!searchObject.Category.Equals("All") && !resItem.ItemObj.Category.Equals(searchObject.Category))
                || ( searchObject.HasFile &&  (!resItem.FileInfoObj.Existence
                    || (searchObject.MaxFileSize < resItem.FileInfoObj.Size)
                    || (searchObject.MinFileSize > resItem.FileInfoObj.Size))
                    )
                || (searchObject.HasComment && resItem.CommentObj.Pros.Equals("") 
                    && resItem.CommentObj.Pros.Equals("") 
                    && resItem.CommentObj.Pros.Equals("")
                    && resItem.CommentObj.ReviewFile.Equals(""))
                || (searchObject.MinRating > resItem.CommentObj.Rating)   
                )
                return false;
            //if not false by those above categories, check tags and bibTexes.
            double matchPercentage = 0.0;
            
            //check tags
            if (searchObject.Tags != null)
            {
                foreach (object tag in searchObject.Tags)
                {
                    int percentage = 0;
                    if (isFound(resItem.CommentObj.Tags, (String)tag,out percentage))
                        matchPercentage += averagePointAward * percentage/100;
                }
            }
            

            //check bibTexes
            foreach(object bts in searchObject.BibTexesStruct)
            {
                if (isFound(resItem.ItemObj.BibTexes, (Common.DataObject.BibTexStruct)bts))
                {
                    if (((Common.DataObject.BibTexStruct)bts).name.ToLower().Equals("title")
                        ||((Common.DataObject.BibTexStruct)bts).name.ToLower().Equals("author"))
                        matchPercentage += 2* averagePointAward;
                    else
                        matchPercentage +=averagePointAward;
                }
            }

            //decide whether the object is considered as matched with the search object
            if (matchPercentage> minMatchingPercentage )
            {
                resItem.MatchPercentage  = matchPercentage;
                return true;
            }
            return false;

        }

        /// <summary>
        /// check a string is found in an arraylist of strings
        /// </summary>
        /// <param name="arlContainer"></param>
        /// <param name="textToLookFor"></param>
        /// <returns></returns>
        private bool isFound(ArrayList arlContainer, String textToLookFor, out int percentage)
        {
            // check if the container is null 
            if (arlContainer == null)
            {
                percentage = 0;
                return false;
            }

            //otherwise
            foreach (object obj in arlContainer)
            {
                if (obj.ToString().Trim().ToLower().Equals(textToLookFor.Trim().ToLower()))
                {
                    percentage = 100; //give 100 percent matching
                    return true;
                }
            }
            //if no exactly equal tag, search for partial match
            foreach (object obj in arlContainer)
            {
                if (obj.ToString().Trim().ToLower().Contains(textToLookFor.Trim().ToLower()))
                {
                    percentage = 50; //give 50% matching
                    return true;
                }
            }
            percentage = 0;
            return false;
        }

        /// <summary>
        /// check a BibTexStruct object is found in an arraylist of BibTexStruct
        /// </summary>
        /// <param name="arlContainer"></param>
        /// <param name="bts"></param>
        /// <returns></returns>
        private bool isFound(ArrayList arlContainer, Common.DataObject.BibTexStruct bts)
        {
            foreach (object obj in arlContainer)
            {
                if ((((Common.DataObject.BibTexStruct)obj).name.ToLower().Trim().Equals(bts.name.ToLower().Trim())
                    ||bts.name.ToLower().Trim().Equals("any"))
                    && ((Common.DataObject.BibTexStruct)obj).value.ToLower().Trim().Contains(bts.value.ToLower().Trim()))
                    return true;
            }
            return false;
        }
    }
}
